#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/vmalloc.h>
#include <linux/miscdevice.h>

#include <linux/slab.h>
#include <linux/uaccess.h>
#include <asm/pgtable.h>
#include <asm/io.h>

#define PAGE_COUNT 1
static struct page *pg;

static unsigned long walk_page_table(struct vm_area_struct *vma)
{
	unsigned long addr = vma->vm_start;
	struct mm_struct *mm = vma->vm_mm;
	unsigned long pa;
	pgd_t *pgd;
	p4d_t *p4d;
	pud_t *pud;
	pmd_t *pmd;
	pte_t *pte;

	pgd = pgd_offset(mm, addr);
	if (pgd_none(*pgd) || pgd_bad(*pgd)) {
		pr_err("wrong pgd\n");
		return 0;
	}

	p4d = p4d_offset(pgd, addr);
	if (p4d_none(*p4d) || p4d_bad(*p4d)) {
		pr_err("wrong pgd\n");
		return 0;
	}

	pud = pud_offset(p4d, addr);
	if (pud_none(*pud) || pud_bad(*pud)) {
		pr_err("wrong pud\n");
		return 0;
	}

	pmd = pmd_offset(pud, addr);
	if (pmd_none(*pmd) || pmd_bad(*pmd)) {
		pr_err("wrong pmd\n");
		return 0;
	}

	pte = pte_offset_kernel(pmd, addr);
	if (pte_none(*pte) || !pte_present(*pte)) {
		pr_err("wrong pte\n");
		return 0;
	}

	//follow_page() can get page
	pa = (pte_pfn(*pte) << PAGE_SHIFT) | (addr & ~PAGE_MASK);

	pr_info("pa=0x%lx, pgd=0x%lx, p4d=0x%lx, pud=0x%lx, pmd=0x%lx, pte=0x%lx\n",
		pa, pgd->pgd, p4d->p4d, pud->pud, pmd->pmd, pte->pte);

	return pa;
}


static int mydrv_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	unsigned long size = vma->vm_end - vma->vm_start;
	unsigned long pfn;

	if (size > PAGE_COUNT * PAGE_SIZE) {
		pr_err("size beyond reserved pages\n");
		return -ENOMEM;
	}

	pfn = page_to_pfn(pg);
	ret = remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot);
	if (ret) {
		pr_err("remap_pfn_range failed\n");
		return ret;
	}

	pr_info("mmap: kva=0x%lx, pa=0x%lx, uva=0x%lx, size=%lu\n",
		(unsigned long)page_to_virt(pg), (unsigned long)page_to_phys(pg),
		vma->vm_start, size);

	walk_page_table(vma);

	return 0;
}

static struct file_operations mydrv_fops = {
	.owner = THIS_MODULE,
	.mmap = mydrv_mmap,
};


static struct miscdevice mydrv_misc_dev = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = "mydev",
	.fops = &mydrv_fops,
};


static int __init mydrv_init(void)
{
	int ret;

	pr_info("%s\n", __func__);

	pg = alloc_pages(GFP_KERNEL, PAGE_COUNT >> 2);
	if (!pg) {
		pr_err("alloc_pages failed\n");
		return -ENOMEM;
	}

	ret = misc_register(&mydrv_misc_dev);
	if (ret) {
		pr_err("%s: misc_register failed, errno=%d\n", __func__, ret);
		__free_pages(pg, PAGE_COUNT >> 2);
		pg = NULL;
		return ret;
	}

	return 0;
}

static void __exit mydrv_exit(void)
{
	pr_info("%s\n", __func__);

	if (pg)
		__free_pages(pg, PAGE_COUNT >> 2);

	misc_deregister(&mydrv_misc_dev);
}

module_init(mydrv_init);
module_exit(mydrv_exit);

MODULE_AUTHOR("mydrv");
MODULE_LICENSE("GPL");
